#include "sdk.h"
#include <wx/bmpbuttn.h>
#include <wx/progdlg.h>
#include <wx/tokenzr.h>
#include "M_Code_StyledTextCtrl.h"
#include "M_Code_colourmanager.h"
#include "M_Code_auibook.h"
#include "editorcolourset.h"
#include "confirmreplacedlg.h"
#include "filefilters.h"
#include "projectfileoptionsdlg.h"
#include "filegroupsandmasks.h"

template<> EditorManager* Mgr<EditorManager>::instance = nullptr;
template<> bool  Mgr<EditorManager>::isShutdown = false;

int ID_NBEditorManager = wxNewId();
int ID_EditorManager = wxNewId();
int idEditorManagerCheckFiles = wxNewId();
bool EditorManager::s_CanShutdown = true;

inline int cbRegisterId( int id ) {
  wxRegisterId( id );
  return id;
}

static const int idNBTabSplitHorz = wxNewId();
static const int idNBTabSplitVert = wxNewId();
static const int idNBTabUnsplit = wxNewId();
static const int idNBTabClose = wxNewId();
static const int idNBTabCloseAll = wxNewId();
static const int idNBTabCloseAllOthers = wxNewId();
static const int idNBTabSave = wxNewId();
static const int idNBTabSaveAll = wxNewId();
static const int idNBSwapHeaderSource = wxNewId();
static const int idNBTabOpenContainingFolder = wxNewId();
static const int idNBTabTop = wxNewId();
static const int idNBTabBottom = wxNewId();
static const int idNBProperties = wxNewId();
static const int idNBAddFileToProject = wxNewId();
static const int idNBRemoveFileFromProject   = wxNewId();
static const int idNBShowFileInTree          = wxNewId();
static const int EditorMaxSwitchTo           = 255;
static const int idNBSwitchFile1             = wxNewId();
static const int idNBSwitchFileMax           = cbRegisterId( idNBSwitchFile1 + EditorMaxSwitchTo - 1 );

struct EditorManagerInternalData {
  EditorManagerInternalData( EditorManager* owner )
    : m_pOwner( owner ) {}
  EditorManager* m_pOwner;
  bool m_SetFocusFlag;
};

BEGIN_EVENT_TABLE( EditorManager, wxEvtHandler )
  EVT_APP_STARTUP_DONE( EditorManager::OnAppDoneStartup )
  EVT_APP_START_SHUTDOWN( EditorManager::OnAppStartShutdown )
  EVT_AUINOTEBOOK_PAGE_CHANGED( ID_NBEditorManager, EditorManager::OnPageChanged )
  EVT_AUINOTEBOOK_PAGE_CHANGING( ID_NBEditorManager, EditorManager::OnPageChanging )
  EVT_AUINOTEBOOK_PAGE_CLOSE( ID_NBEditorManager, EditorManager::OnPageClose )
  EVT_AUINOTEBOOK_TAB_RIGHT_UP( ID_NBEditorManager, EditorManager::OnPageContextMenu )
  EVT_MENU_RANGE( idNBSwitchFile1, idNBSwitchFileMax, EditorManager::OnGenericContextMenuHandler )
  EVT_MENU( idNBTabSplitHorz, EditorManager::OnGenericContextMenuHandler )
  EVT_MENU( idNBTabSplitVert, EditorManager::OnGenericContextMenuHandler )
  EVT_MENU( idNBTabUnsplit, EditorManager::OnGenericContextMenuHandler )
  EVT_MENU( idNBTabTop, EditorManager::OnTabPosition )
  EVT_MENU( idNBTabBottom, EditorManager::OnTabPosition )
  EVT_MENU( idNBTabClose, EditorManager::OnClose )
  EVT_MENU( idNBTabCloseAll, EditorManager::OnCloseAll )
  EVT_MENU( idNBTabCloseAllOthers, EditorManager::OnCloseAllOthers )
  EVT_MENU( idNBTabOpenContainingFolder, EditorManager::OnOpenContainingFolder )
  EVT_MENU( idNBTabSave, EditorManager::OnSave )
  EVT_MENU( idNBTabSaveAll, EditorManager::OnSaveAll )
  EVT_MENU( idNBSwapHeaderSource, EditorManager::OnSwapHeaderSource )
  EVT_MENU( idNBProperties, EditorManager::OnProperties )
  EVT_MENU( idNBAddFileToProject, EditorManager::OnAddFileToProject )
  EVT_MENU( idNBRemoveFileFromProject, EditorManager::OnRemoveFileFromProject )
  EVT_MENU( idNBShowFileInTree, EditorManager::OnShowFileInTree )
  EVT_MENU( idEditorManagerCheckFiles, EditorManager::OnCheckForModifiedFiles )
END_EVENT_TABLE()

EditorManager::EditorManager()
  : m_pNotebook( nullptr ),
    m_pNotebookStackHead( new cbNotebookStack ),
    m_pNotebookStackTail( m_pNotebookStackHead ),
    m_nNotebookStackSize( 0 ),
    m_isCheckingForExternallyModifiedFiles( false ) {
  m_pData = new EditorManagerInternalData( this );
  m_pNotebook = new M_Code_AuiNotebook( Manager::Get()->GetAppWindow(), ID_NBEditorManager, wxDefaultPosition, wxDefaultSize,
                                        wxAUI_NB_DEFAULT_STYLE | wxAUI_NB_WINDOWLIST_BUTTON | wxNO_FULL_REPAINT_ON_RESIZE | wxCLIP_CHILDREN );
  if( Manager::Get()->GetConfigManager( _T( "app" ) )->ReadBool( _T( "/environment/editor_tabs_bottom" ), false ) ) {
    m_pNotebook->SetWindowStyleFlag( m_pNotebook->GetWindowStyleFlag() | wxAUI_NB_BOTTOM );
  }
  m_Theme = new EditorColourSet( Manager::Get()->GetConfigManager( _T( "editor" ) )->Read( _T( "/colour_sets/active_colour_set" ), COLORSET_DEFAULT ) );
  Manager::Get()->GetAppWindow()->PushEventHandler( this );
  m_Zoom = Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadInt( _T( "/zoom" ) );
  Manager::Get()->RegisterEventSink( cbEVT_BUILDTARGET_SELECTED, new cbEventFunctor<EditorManager, CodeBlocksEvent>( this, &EditorManager::CollectDefines ) );
  Manager::Get()->RegisterEventSink( cbEVT_PROJECT_ACTIVATE, new cbEventFunctor<EditorManager, CodeBlocksEvent>( this, &EditorManager::CollectDefines ) );
  Manager::Get()->RegisterEventSink( cbEVT_WORKSPACE_LOADING_COMPLETE, new cbEventFunctor<EditorManager, CodeBlocksEvent>( this, &EditorManager::CollectDefines ) );
  ColourManager *colours = Manager::Get()->GetColourManager();
  colours->RegisterColour( _T( "Editor" ), _T( "插入符号" ), _T( "editor_caret" ), wxColor( 206, 250, 72 ) );
  colours->RegisterColour( _T( "Editor" ), _T( "右边距" ), _T( "editor_gutter" ), *wxLIGHT_GREY );
  colours->RegisterColour( _T( "Editor" ), _T( "行号前景颜色" ), _T( "editor_linenumbers_fg" ), wxSystemSettings::GetColour( wxSYS_COLOUR_BTNTEXT ) );
  colours->RegisterColour( _T( "Editor" ), _T( "行号背景色" ), _T( "editor_linenumbers_bg" ), wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
  colours->RegisterColour( _T( "Editor" ), _T( "页边空白颜色" ), _T( "editor_margin_chrome" ), wxSystemSettings::GetColour( wxSYS_COLOUR_3DFACE ) );
  colours->RegisterColour( _T( "Editor" ), _T( "页边空白突出颜色" ), _T( "editor_margin_chrome_highlight" ), wxSystemSettings::GetColour( wxSYS_COLOUR_3DHIGHLIGHT ) );
  colours->RegisterColour( _T( "Editor" ), _T( "空白" ), _T( "editor_whitespace" ), wxColor( 195, 195, 195 ) );
}

EditorManager::~EditorManager() {
  DeleteNotebookStack();
  delete m_pNotebookStackHead;
  delete m_Theme;
  delete m_pData;
  Manager::Get()->GetConfigManager( _T( "editor" ) )->Write( _T( "/zoom" ), m_Zoom );
}

cbNotebookStack* EditorManager::GetNotebookStack() {
  bool found = false;
  wxWindow* wnd;
  cbNotebookStack* body;
  cbNotebookStack* prev_body;
  while( m_nNotebookStackSize != m_pNotebook->GetPageCount() ) {
    if( m_nNotebookStackSize < m_pNotebook->GetPageCount() ) {
      for( size_t i = 0; i < m_pNotebook->GetPageCount(); ++i ) {
        wnd = m_pNotebook->GetPage( i );
        found = false;
        for( body = m_pNotebookStackHead->next; body != NULL; body = body->next ) {
          if( wnd == body->window ) {
            found = true;
            break;
          }
        }
        if( !found ) {
          m_pNotebookStackTail->next = new cbNotebookStack( wnd );
          m_pNotebookStackTail = m_pNotebookStackTail->next;
          ++m_nNotebookStackSize;
        }
      }
    }
    if( m_nNotebookStackSize > m_pNotebook->GetPageCount() ) {
      for( prev_body = m_pNotebookStackHead, body = prev_body->next; body != NULL; prev_body = body, body = body->next ) {
        if( m_pNotebook->GetPageIndex( body->window ) == wxNOT_FOUND ) {
          prev_body->next = body->next;
          delete body;
          --m_nNotebookStackSize;
          body = prev_body;
        }
      }
    }
  }
  return m_pNotebookStackHead->next;
}

void EditorManager::DeleteNotebookStack() {
  cbNotebookStack* tmp;
  while( m_pNotebookStackHead->next ) {
    tmp = m_pNotebookStackHead->next;
    m_pNotebookStackHead->next = tmp->next;
    delete tmp;
  }
  m_pNotebookStackTail = m_pNotebookStackHead;
  m_nNotebookStackSize = 0;
}

void EditorManager::RebuildNotebookStack() {
  DeleteNotebookStack();
  for( size_t i = 0; i < m_pNotebook->GetPageCount(); ++i ) {
    m_pNotebookStackTail->next = new cbNotebookStack( m_pNotebook->GetPage( i ) );
    m_pNotebookStackTail = m_pNotebookStackTail->next;
    ++m_nNotebookStackSize;
  }
}

void EditorManager::RecreateOpenEditorStyles() {
  for( size_t i = 0; i < m_pNotebook->GetPageCount(); ++i ) {
    M_Code_Editor* ed = InternalGetBuiltinEditor( i );
    if( ed ) {
      bool saveSuccess = ed->SaveFoldState();
      ed->SetEditorStyle();
      if( saveSuccess ) {
        ed->FixFoldState();
      }
    }
  }
}

int EditorManager::GetEditorsCount() {
  return m_pNotebook->GetPageCount();
}

EditorBase* EditorManager::InternalGetEditorBase( int page ) {
  if( page >= 0 && page < ( int )m_pNotebook->GetPageCount() ) {
    return static_cast<EditorBase*>( m_pNotebook->GetPage( page ) );
  }
  return nullptr;
}

M_Code_Editor* EditorManager::InternalGetBuiltinEditor( int page ) {
  EditorBase* eb = InternalGetEditorBase( page );
  if( eb && eb->IsBuiltinEditor() ) {
    return ( M_Code_Editor* )eb;
  }
  return nullptr;
}

M_Code_Editor* EditorManager::GetBuiltinEditor( EditorBase* eb ) {
  return eb && eb->IsBuiltinEditor() ? ( M_Code_Editor* )eb : nullptr;
}

EditorBase* EditorManager::IsOpen( const wxString& filename ) {
  wxString uFilename = UnixFilename( realpath( filename ) );
  for( size_t i = 0; i < m_pNotebook->GetPageCount(); ++i ) {
    EditorBase* eb = InternalGetEditorBase( i );
    if( !eb ) {
      continue;
    }
    wxString fname = eb->GetFilename();
    if( fname.IsSameAs( uFilename, true ) || fname.IsSameAs( g_EditorModified + uFilename, true ) ) {
      return eb;
    }
  }
  return NULL;
}

EditorBase* EditorManager::GetEditor( int index ) {
  return InternalGetEditorBase( index );
}

void EditorManager::SetColourSet( EditorColourSet* theme ) {
  if( m_Theme ) {
    delete m_Theme;
  }
  m_Theme = new EditorColourSet( *theme );
  for( size_t i = 0; i < m_pNotebook->GetPageCount(); ++i ) {
    M_Code_Editor* ed = InternalGetBuiltinEditor( i );
    if( ed ) {
      ed->SetColourSet( m_Theme );
    }
  }
}

M_Code_Editor* EditorManager::Open( const wxString& filename, int pos, ProjectFile* data ) {
  return Open( nullptr, filename, pos, data );
}

M_Code_Editor* EditorManager::Open( LoaderBase* fileLdr, const wxString& filename, int, ProjectFile* data ) {
  bool can_updateui = !GetActiveEditor() || !Manager::Get()->GetProjectManager()->IsLoading();
  wxFileName fn( realpath( filename ) );
  NormalizePath( fn, wxEmptyString );
  wxString fname = UnixFilename( fn.GetFullPath() );
  if( !wxFileExists( fname ) ) {
    return nullptr;
  }
  s_CanShutdown = false;
  EditorBase* eb = IsOpen( fname );
  M_Code_Editor* ed = nullptr;
  if( eb ) {
    if( eb->IsBuiltinEditor() ) {
      ed = ( M_Code_Editor* )eb;
    } else {
      s_CanShutdown = true;
      return nullptr;
    }
  }
  if( !ed ) {
    if( !fileLdr ) {
      fileLdr = Manager::Get()->GetFileManager()->Load( filename );
    }
    if( fileLdr ) {
      ed = new M_Code_Editor( m_pNotebook, fileLdr, fname, m_Theme );
      if( ed->IsOK() ) {
        AddEditorBase( ed );
      } else {
        ed->Destroy();
        ed = nullptr;
      }
    }
  }
  if( ed && !ed->GetProjectFile() ) {
    if( data ) {
      Manager::Get()->GetLogManager()->DebugLog( _T( "项目数据集 " ) + data->file.GetFullPath() );
    } else
    { Manager::Get()->GetProjectManager()->FindProjectForFile( ed->GetFilename(), &data, false, false ); }
    if( data ) {
      ed->SetProjectFile( data, true );
    }
  }
  if( can_updateui ) {
    if( ed ) {
      SetActiveEditor( ed );
      ed->GetControl()->SetFocus();
    }
  }
  s_CanShutdown = true;
  return ed;
}

EditorBase* EditorManager::GetActiveEditor() {
  if( m_pNotebook->GetPageCount() > 0 ) {
    return InternalGetEditorBase( m_pNotebook->GetSelection() );
  }
  return nullptr;
}

void EditorManager::ActivateNext() {
  m_pNotebook->AdvanceSelection( true );
}

void EditorManager::ActivatePrevious() {
  m_pNotebook->AdvanceSelection( false );
}

void EditorManager::SetActiveEditor( EditorBase* ed ) {
  if( !ed ) {
    return;
  }
  int page = FindPageFromEditor( ed );
  if( page != -1 ) {
    int sel = m_pNotebook->GetSelection();
    m_pNotebook->SetSelection( page );
    EditorBase* eb_old = nullptr;
    if( sel >= 0 ) {
      eb_old = static_cast<EditorBase*>( m_pNotebook->GetPage( sel ) );
    }
    CodeBlocksEvent evt( cbEVT_EDITOR_SWITCHED, -1, nullptr, ed, nullptr, eb_old );
    Manager::Get()->GetPluginManager()->NotifyPlugins( evt );
    CodeBlocksEvent evt2( cbEVT_EDITOR_ACTIVATED, -1, nullptr, ed );
    Manager::Get()->GetPluginManager()->NotifyPlugins( evt2 );
  }
  if( ed->IsBuiltinEditor() ) {
    static_cast<M_Code_Editor*>( ed )->GetControl()->SetFocus();
  }
}

M_Code_Editor* EditorManager::New( const wxString& newFileName ) {
  if( !newFileName.IsEmpty() && !wxFileExists( newFileName ) && wxDirExists( wxPathOnly( newFileName ) ) ) {
    wxFile f( newFileName, wxFile::write );
    if( !f.IsOpened() ) {
      return nullptr;
    }
  }
  M_Code_Editor* ed = new M_Code_Editor( m_pNotebook, newFileName );
  wxString key;
  key.Printf( _T( "/default_code/set%d" ), ( int )FileTypeOf( ed->GetFilename() ) );
  wxString code = Manager::Get()->GetConfigManager( _T( "editor" ) )->Read( key, wxEmptyString );
  Manager::Get()->GetMacrosManager()->ReplaceMacros( code );
  ed->GetControl()->SetText( code );
  ed->SetColourSet( m_Theme );
  AddEditorBase( ed );
  ed->Show( true );
  SetActiveEditor( ed );
  CodeBlocksEvent evt( cbEVT_EDITOR_OPEN, -1, nullptr, ed );
  Manager::Get()->GetPluginManager()->NotifyPlugins( evt );
  return ed;
}

void EditorManager::AddCustomEditor( EditorBase* eb ) {
  AddEditorBase( eb );
}

void EditorManager::RemoveCustomEditor( EditorBase* eb ) {
  RemoveEditorBase( eb, false );
}

void EditorManager::AddEditorBase( EditorBase* eb ) {
  int page = FindPageFromEditor( eb );
  if( page == -1 ) {
    wxFileName fn( eb->GetTitle() );
    m_pNotebook->AddPage( eb, fn.GetFullName(), true );
  }
}

void EditorManager::RemoveEditorBase( EditorBase* eb, M_Code_unused bool deleteObject ) {
  int page = FindPageFromEditor( eb );
  if( page != -1 && !Manager::IsAppShuttingDown() ) {
    m_pNotebook->RemovePage( page );
  }
}

bool EditorManager::UpdateProjectFiles( M_Code_Project* project ) {
  for( size_t i = 0; i < m_pNotebook->GetPageCount(); ++i ) {
    M_Code_Editor* ed = InternalGetBuiltinEditor( i );
    if( !ed ) {
      continue;
    }
    ProjectFile* pf = ed->GetProjectFile();
    if( !pf ) {
      continue;
    }
    if( pf->GetParentProject() != project ) {
      continue;
    }
    pf->editorTabPos = m_pNotebook->GetTabPositionFromIndex( i ) + 1;
    ed->UpdateProjectFile();
  }
  return true;
}

bool EditorManager::CloseAll( bool dontsave ) {
  EditorBase *startPage = GetEditor( _T( "Start here" ) );
  std::vector<EditorBase*> editors;
  editors.reserve( m_pNotebook->GetPageCount() );
  for( size_t ii = 0; ii < m_pNotebook->GetPageCount(); ++ii ) {
    EditorBase *eb = static_cast<EditorBase*>( m_pNotebook->GetPage( ii ) );
    if( startPage != eb ) {
      editors.push_back( eb );
    }
  }
  return CloseEditors( editors, dontsave );
}

bool EditorManager::QueryCloseAll() {
  for( int i = m_pNotebook->GetPageCount() - 1; i >= 0; --i ) {
    EditorBase* eb = InternalGetEditorBase( i );
    if( eb && !QueryClose( eb ) ) {
      return false;
    }
  }
  return true;
}

bool EditorManager::CloseAllExcept( EditorBase* editor, bool dontsave ) {
  std::vector<EditorBase*> editors;
  editors.reserve( m_pNotebook->GetPageCount() );
  for( size_t ii = 0; ii < m_pNotebook->GetPageCount(); ++ii ) {
    editors.push_back( InternalGetEditorBase( ii ) );
  }
  editors.erase( std::remove( editors.begin(), editors.end(), editor ), editors.end() );
  return CloseEditors( editors, dontsave );
}

bool EditorManager::CloseAllInTabCtrl( bool dontsave ) {
  std::vector<EditorBase*> editors;
  GetEditorsInTabCtrl( editors, GetActiveEditor() );
  return CloseEditors( editors, dontsave );
}

bool EditorManager::CloseAllInTabCtrlExcept( EditorBase* editor, bool dontsave ) {
  std::vector<EditorBase*> editors;
  GetEditorsInTabCtrl( editors, GetActiveEditor() );
  editors.erase( std::remove( editors.begin(), editors.end(), editor ), editors.end() );
  return CloseEditors( editors, dontsave );
}

void EditorManager::GetEditorsInTabCtrl( std::vector<EditorBase*> &editors, EditorBase *editor ) {
  std::vector<wxWindow*> windows;
  m_pNotebook->GetPagesInTabCtrl( windows, editor );
  if( !windows.empty() ) {
    editors.reserve( windows.size() );
    for( std::vector<wxWindow*>::const_iterator it = windows.begin(); it != windows.end(); ++it ) {
      editors.push_back( static_cast<EditorBase*>( *it ) );
    }
  }
}

bool EditorManager::CloseEditors( const std::vector<EditorBase*> &editors, bool dontsave ) {
  if( !dontsave ) {
    for( std::vector<EditorBase*>::const_iterator it = editors.begin(); it != editors.end(); ++it ) {
      EditorBase* eb = *it;
      if( eb && !QueryClose( eb ) ) {
        return false;
      }
    }
  }
  m_pNotebook->Freeze();
  int count = editors.size();
  for( std::vector<EditorBase*>::const_reverse_iterator it = editors.rbegin(); it != editors.rend(); ++it ) {
    EditorBase* eb = *it;
    if( eb && Close( eb, true ) ) {
      --count;
    }
  }
  m_pNotebook->Thaw();
  return ( count == 0 );
}

bool EditorManager::CloseActive( bool dontsave ) {
  return Close( GetActiveEditor(), dontsave );
}

bool EditorManager::QueryClose( EditorBase *ed ) {
  if( !ed ) {
    return true;
  }
  return ed->QueryClose();
}

int EditorManager::FindPageFromEditor( EditorBase* eb ) {
  if( !m_pNotebook || !eb ) {
    return -1;
  }
  for( size_t i = 0; i < m_pNotebook->GetPageCount(); ++i ) {
    if( m_pNotebook->GetPage( i ) == eb ) {
      return i;
    }
  }
  return -1;
}

bool EditorManager::Close( const wxString& filename, bool dontsave ) {
  return Close( IsOpen( filename ), dontsave );
}

bool EditorManager::Close( EditorBase* editor, bool dontsave ) {
  if( editor ) {
    int idx = FindPageFromEditor( editor );
    if( idx != -1 ) {
      if( !dontsave )
        if( !QueryClose( editor ) ) {
          return false;
        }
      m_pNotebook->DeletePage( idx );
    }
  }
  return true;
}

bool EditorManager::Close( int index, bool dontsave ) {
  EditorBase* ed = InternalGetEditorBase( index );
  if( ed ) {
    return Close( ed, dontsave );
  }
  return false;
}

bool EditorManager::Save( const wxString& filename ) {
  EditorBase* ed = IsOpen( filename );
  if( ed ) {
    if( !ed->Save() ) {
      return false;
    }
    return true;
  }
  return true;
}

bool EditorManager::Save( int index ) {
  EditorBase* ed = InternalGetEditorBase( index );
  if( ed ) {
    if( !ed->Save() ) {
      return false;
    }
    return true;
  }
  return false;
}

bool EditorManager::SaveActive() {
  EditorBase* ed = GetActiveEditor();
  if( ed ) {
    if( !ed->Save() ) {
      return false;
    }
    return true;
  }
  return true;
}

bool EditorManager::SaveAs( int index ) {
  EditorBase* eb = InternalGetEditorBase( index );
  if( !eb ) {
    return false;
  }
  return eb->SaveAs();
}

bool EditorManager::SaveActiveAs() {
  EditorBase* eb = GetActiveEditor();
  if( eb ) {
    return eb->SaveAs();
  }
  return true;
}

bool EditorManager::SaveAll() {
  for( size_t i = 0; i < m_pNotebook->GetPageCount(); ++i ) {
    EditorBase* ed = InternalGetEditorBase( i );
    if( ed && ed->GetModified() && !ed->Save() ) {
      wxString msg;
      msg.Printf( _T( "文件 %s 无法保存..." ), ed->GetFilename().c_str() );
      msging( msg, _T( "保存文件时出错" ), wxICON_ERROR );
    }
  }
  return true;
}

void EditorManager::CheckForExternallyModifiedFiles() {
  if( m_isCheckingForExternallyModifiedFiles ) {
    return;
  }
  m_isCheckingForExternallyModifiedFiles = true;
  bool reloadAll = false;
  wxArrayString failedFiles;
  for( size_t i = 0; i < m_pNotebook->GetPageCount(); ++i ) {
    M_Code_Editor* ed = InternalGetBuiltinEditor( i );
    bool b_modified = false;
    if( !ed ) {
      EditorBase* eb = InternalGetEditorBase( i );
      if( eb ) {
        ProjectFile* pf = nullptr;
        wxFileName fn( eb->GetFilename() );
        fn.MakeAbsolute();
        Manager::Get()->GetProjectManager()->FindProjectForFile( fn.GetFullPath(), &pf, false, false );
        bool readOnly = eb->IsReadOnly() || ( fn.FileExists() && !wxFile::Access( fn.GetFullPath(), wxFile::write ) );
        MarkReadOnly( i, readOnly );
        if( pf ) {
          if( !fn.FileExists() ) {
            if( pf->GetFileState() != fvsMissing ) {
              wxWindow* win = wxWindow::GetCapture();
              if( win )
              { win->ReleaseMouse(); }
              wxString msg;
              msg.Printf( _T( "%s 已被删除, 或不再可用.\n"
                              "是否尝试将文件保存到磁盘?\n"
                              "如果你关闭它, 它很可能会丢失 !\n"
                              "如果取消此对话框, 你得照顾好自己 !\n"
                              "是：保存文件, No: close it, Cancel: keep your fingers crossed." ), eb->GetFilename().c_str() );
              int ret = msging( msg, _T( "File changed!" ), wxICON_QUESTION | wxYES_NO | wxCANCEL );
              switch( ret ) {
                case wxID_YES: {
                  eb->Save();
                  break;
                }
                case wxID_NO: {
                  pf->SetFileState( fvsMissing );
                  eb->Close();
                  break;
                }
                case wxID_CANCEL:
                default:
                  eb->SetModified( true );
                  pf->SetFileState( fvsMissing );
                  break;
              }
            }
          } else
          { pf->SetFileState( readOnly ? fvsReadOnly : fvsNormal ); }
        }
        continue;
      }
    }
    if( !ed->IsOK() ) {
      continue;
    }
    ProjectFile* pf = ed->GetProjectFile();
    if( pf ) {
      M_Code_Project* prj = pf->GetParentProject();
      if( prj && !prj->GetCheckForExternallyModifiedFiles() ) {
        continue;
      }
    }
    if( !wxFileExists( ed->GetFilename() ) ) {
      if( ed->GetModified() ) {
        continue;
      }
      wxWindow* win = wxWindow::GetCapture();
      if( win ) {
        win->ReleaseMouse();
      }
      wxString msg;
      msg.Printf( _T( "%s 已被删除, 或不再可用.\n"
                      "你想把文件打开吗?\n"
                      "Yes to keep the file, No to close it." ), ed->GetFilename().c_str() );
      if( msging( msg, _T( "File changed!" ), wxICON_QUESTION | wxYES_NO ) == wxID_YES ) {
        ed->SetModified( true );
      } else {
        if( pf ) {
          pf->SetFileState( fvsMissing );
        }
        ed->Close();
      }
      continue;
    }
    wxFileName fname( ed->GetFilename() );
    wxDateTime last = fname.GetModificationTime();
    if( ed->GetControl()->GetReadOnly() &&
        wxFile::Access( ed->GetFilename().c_str(), wxFile::write ) ) {
      b_modified = false;
      ed->SetReadOnly( false );
      MarkReadOnly( i, false );
      if( pf ) {
        pf->SetFileState( fvsNormal );
      }
    }
    if( !ed->GetControl()->GetReadOnly() &&
        !wxFile::Access( ed->GetFilename().c_str(), wxFile::write ) ) {
      b_modified = false;
      ed->SetReadOnly( true );
      MarkReadOnly( i );
      if( pf ) {
        pf->SetFileState( fvsReadOnly );
      }
    }
    if( last.IsLaterThan( ed->GetLastModificationTime() ) ) {
      b_modified = true;
    }
    if( b_modified ) {
      int ret = -1;
      if( !reloadAll ) {
        wxString msg;
        msg.Printf( _T( "文件 %s 在IDE外修改...\n是否要重新加载（将丢失所有未保存的工作）?" ), ed->GetFilename().c_str() );
        ConfirmReplaceDlg dlg( Manager::Get()->GetAppWindow(), false, msg );
        dlg.SetTitle( _T( "再装入文件?" ) );
        dlg.GetSizer()->SetSizeHints( &dlg );
        PlaceWindow( &dlg );
        wxWindow* win = wxWindow::GetCapture();
        if( win ) {
          win->ReleaseMouse();
        }
        ret = dlg.ShowModal();
        reloadAll = ret == crAll;
      }
      if( reloadAll || ret == crYes ) {
        if( !ed->Reload() ) {
          failedFiles.Add( ed->GetFilename() );
        }
      } else if( ret == crCancel ) {
        break;
      } else if( ret == crNo ) {
        ed->Touch();
      }
    }
  }
  SetActiveEditor( GetActiveEditor() );
  if( failedFiles.GetCount() ) {
    wxWindow* win = wxWindow::GetCapture();
    if( win ) {
      win->ReleaseMouse();
    }
    wxString msg;
    msg.Printf( _T( "无法重新加载所有文件:\n\n%s" ), GetStringFromArray( failedFiles, _T( "\n" ) ).c_str() );
    msging( msg, _T( "错误" ), wxICON_ERROR );
  }
  m_isCheckingForExternallyModifiedFiles = false;
}

void EditorManager::MarkReadOnly( int page, bool readOnly ) {
  if( page > -1 ) {
    wxBitmap bmp = readOnly ? M_Code_LoadBitmap( ConfigManager::GetDataFolder() + _T( "/images/readonly.png" ), wxBITMAP_TYPE_PNG ) : wxNullBitmap;
    if( m_pNotebook ) {
      m_pNotebook->SetPageBitmap( page, bmp );
    }
  }
}

bool EditorManager::IsHeaderSource( const wxFileName& candidateFile, const wxFileName& activeFile, FileType ftActive, bool& isCandidate ) {
  if( candidateFile.GetName().CmpNoCase( activeFile.GetName() ) == 0 ) {
    isCandidate = ( candidateFile.GetName() != activeFile.GetName() );
    FileType ftTested = FileTypeOf( candidateFile.GetFullName() );
    if( ( ( ftActive == ftHeader ) && ( ftTested == ftSource ) )
        || ( ( ftActive == ftSource ) && ( ftTested == ftHeader ) )
        || ( ( ftActive == ftHeader ) && ( ftTested == ftTemplateSource ) )
        || ( ( ftActive == ftTemplateSource ) && ( ftTested == ftHeader ) ) ) {
      if( candidateFile.GetPath() != activeFile.GetPath() ) {
        wxArrayString fileArray;
        wxDir::GetAllFiles( candidateFile.GetPath( wxPATH_GET_VOLUME ), &fileArray, candidateFile.GetName() + _T( ".*" ), wxDIR_FILES | wxDIR_HIDDEN );
        for( unsigned i = 0; i < fileArray.GetCount(); i++ )
          if( wxFileName( fileArray[i] ).GetFullName() == activeFile.GetFullName() ) {
            return false;
          }
      }
      if( candidateFile.FileExists() ) {
        return true;
      }
    }
  }
  return false;
}

wxFileName EditorManager::FindHeaderSource( const wxArrayString& candidateFilesArray, const wxFileName& activeFile, bool& isCandidate ) {
  FileType ftActive = FileTypeOf( activeFile.GetFullName() );
  bool extStartsWithCapital = wxIsupper( activeFile.GetExt()[0] );
  wxFileName candidateFile;
  for( unsigned i = 0; i < candidateFilesArray.GetCount(); ++i ) {
    wxFileName currentCandidateFile( candidateFilesArray[i] );
    if( IsHeaderSource( currentCandidateFile, activeFile, ftActive, isCandidate ) ) {
      bool isUpper = wxIsupper( currentCandidateFile.GetExt()[0] );
      if( isUpper == extStartsWithCapital && !isCandidate ) {
        return currentCandidateFile;
      } else {
        candidateFile = currentCandidateFile;
      }
    }
  }
  isCandidate = true;
  return candidateFile;
}

struct OpenContainingFolderData {
  wxString command;
  bool supportSelect;

  OpenContainingFolderData() : supportSelect( false ) {}
  OpenContainingFolderData( const wxString &command, bool select ) : command( command ), supportSelect( select ) {}
};


static OpenContainingFolderData detectNautilus( const wxString &command, ConfigManager* appConfig ) {
  wxString fileManager;
  if( command == cbDEFAULT_OPEN_FOLDER_CMD ) {
    const wxString shell = appConfig->Read( _T( "/console_shell" ), DEFAULT_CONSOLE_SHELL );
    const wxString cmdGetManager = shell + _T( " 'xdg-mime query default inode/directory'" );
    wxArrayString output, errors;
    wxExecute( cmdGetManager, output, errors, wxEXEC_SYNC );
    if( output.empty() ) {
      return OpenContainingFolderData( command, false );
    }
    fileManager = output[0];
  } else {
    fileManager = command;
  }
  Manager::Get()->GetLogManager()->LogInf( F( _T( "文件管理器: '%s'" ), fileManager.wx_str() ) );
  if( fileManager.find( _T( "nautilus" ) ) == wxString::npos ) {
    return OpenContainingFolderData( command, false );
  }
  if( fileManager.EndsWith( _T( ".desktop" ) ) ) {
    fileManager = _T( "nautilus" );
  }
  wxArrayString output, errors;
  wxExecute( fileManager + _T( " --version" ), output, errors, wxEXEC_SYNC );
  if( output.empty() ) {
    return OpenContainingFolderData( command, false );
  }
  const wxString prefix( _T( "GNOME nautilus " ) );
  const wxString firstLine = output[0].Trim( true ).Trim( false );
  Manager::Get()->GetLogManager()->LogInf( F( _T( "Nautilus版本是: '%s'" ), firstLine.wx_str() ) );
  if( firstLine.StartsWith( prefix ) ) {
    wxArrayString versionTokens = wxStringTokenize( firstLine.substr( prefix.length() ), _T( "." ) );
    int fullVersion = 0;
    int multiplier = 1;
    for( int ii = versionTokens.GetCount() - 1; ii >= 0; --ii ) {
      long number;
      versionTokens[ii].ToLong( &number );
      fullVersion += number * multiplier;
      multiplier *= 100;
    }
    if( fullVersion >= 30002 ) {
      return OpenContainingFolderData( fileManager + _T( " --select" ), true );
    }
  }
  return OpenContainingFolderData( command, false );
}

bool EditorManager::OpenContainingFolder() {
  M_Code_Editor* ed = GetBuiltinEditor( GetActiveEditor() );
  if( !ed ) {
    return false;
  }
  ConfigManager* cfg = Manager::Get()->GetConfigManager( _T( "app" ) );
  const wxString &command = cfg->Read( _T( "open_containing_folder" ), cbDEFAULT_OPEN_FOLDER_CMD );
  OpenContainingFolderData cmdData = detectNautilus( command, cfg );
  const wxString& fullPath = ed->GetFilename();
  cmdData.command << _T( " " );
  if( !cmdData.supportSelect ) {
    wxString splitPath;
    wxFileName::SplitPath( fullPath, &splitPath, nullptr, nullptr );
    cmdData.command << splitPath;
  } else {
    cmdData.command << fullPath;
  }
  wxExecute( cmdData.command );
  Manager::Get()->GetLogManager()->DebugLog( F( _T( "执行打开文件夹的命令: '%s'" ),
      cmdData.command.wx_str() ) );
  return true;
}

bool EditorManager::SwapActiveHeaderSource() {
  M_Code_Editor* ed = GetBuiltinEditor( GetActiveEditor() );
  if( !ed ) {
    return false;
  }
  ProjectManager *pm = Manager::Get()->GetProjectManager();
  if( !pm ) {
    return false;
  }
  FileType ft = FileTypeOf( ed->GetFilename() );
  if( ft != ftHeader && ft != ftSource && ft != ftTemplateSource ) {
    return false;
  }
  M_Code_Project* project = nullptr;
  ProjectFile* opf = ed->GetProjectFile();
  if( opf ) {
    project = opf->GetParentProject();
  }
  if( !project ) {
    project = pm->GetActiveProject();
  }
  wxFileName theFile( ed->GetFilename() );
  wxFileName candidateFile;
  bool isCandidate;
  wxArrayString fileArray;
  wxDir::GetAllFiles( theFile.GetPath( wxPATH_GET_VOLUME ), &fileArray, theFile.GetName() + _T( ".*" ), wxDIR_FILES | wxDIR_HIDDEN );
  wxFileName currentCandidateFile = FindHeaderSource( fileArray, theFile, isCandidate );
  if( isCandidate ) {
    candidateFile = currentCandidateFile;
  } else if( currentCandidateFile.IsOk() ) {
    M_Code_Editor* newEd = Open( currentCandidateFile.GetFullPath() );
    if( newEd != nullptr ) {
      return true;
    }
  }
  fileArray.Clear();
  for( int i = 0; i < GetEditorsCount(); ++i ) {
    M_Code_Editor* edit = GetBuiltinEditor( GetEditor( i ) );
    if( !edit ) {
      continue;
    }
    ProjectFile* pf = edit->GetProjectFile();
    if( !pf ) {
      continue;
    }
    fileArray.Add( pf->file.GetFullPath() );
  }
  currentCandidateFile = FindHeaderSource( fileArray, theFile, isCandidate );
  if( !isCandidate && currentCandidateFile.IsOk() ) {
    M_Code_Editor* newEd = Open( currentCandidateFile.GetFullPath() );
    if( newEd != nullptr ) {
      return true;
    }
  }
  if( project ) {
    fileArray.Clear();
    for( FilesList::iterator it = project->GetFilesList().begin(); it != project->GetFilesList().end(); ++it ) {
      ProjectFile* pf = *it;
      if( !pf ) {
        continue;
      }
      fileArray.Add( pf->file.GetFullPath() );
    }
    currentCandidateFile = FindHeaderSource( fileArray, theFile, isCandidate );
    if( isCandidate && !candidateFile.IsOk() ) {
      candidateFile = currentCandidateFile;
    } else if( currentCandidateFile.IsOk() ) {
      M_Code_Editor* newEd = Open( currentCandidateFile.GetFullPath() );
      if( newEd != nullptr ) {
        return true;
      }
    }
    wxArrayString dirs = project->GetIncludeDirs();
    for( int i = 0; i < project->GetBuildTargetsCount(); ++i ) {
      ProjectBuildTarget* target = project->GetBuildTarget( i );
      if( target ) {
        for( unsigned int ti = 0; ti < target->GetIncludeDirs().GetCount(); ++ti ) {
          wxString dir = target->GetIncludeDirs()[ti];
          if( dirs.Index( dir ) == wxNOT_FOUND ) {
            dirs.Add( dir );
          }
        }
      }
    }
    for( unsigned int i = 0; i < dirs.GetCount(); ++i ) {
      wxString dir = dirs[i];
      Manager::Get()->GetMacrosManager()->ReplaceMacros( dir );
      wxFileName dname( dir );
      if( !dname.IsAbsolute() ) {
        dname.Normalize( wxPATH_NORM_ALL & ~wxPATH_NORM_CASE, project->GetBasePath() );
      }
      fileArray.Clear();
      wxDir::GetAllFiles( dname.GetPath(), &fileArray, theFile.GetName() + _T( ".*" ), wxDIR_FILES | wxDIR_HIDDEN );
      currentCandidateFile = FindHeaderSource( fileArray, theFile, isCandidate );
      if( isCandidate ) {
        candidateFile = currentCandidateFile;
      } else if( currentCandidateFile.IsOk() ) {
        M_Code_Editor* newEd = Open( currentCandidateFile.GetFullPath() );
        if( newEd != nullptr ) {
          return true;
        }
      }
    }
  }
  if( candidateFile.IsOk() ) {
    M_Code_Editor* newEd = Open( candidateFile.GetFullPath() );
    if( newEd != nullptr ) {
      return true;
    }
  }
  if( opf ) {
    ProjectFile* candidatePF = opf->AutoGeneratedBy();
    if( !candidatePF && !opf->generatedFiles.empty() ) {
      candidatePF = opf->generatedFiles.front();
    }
    if( candidatePF ) {
      M_Code_Editor* newEd = Open( candidatePF->file.GetFullPath() );
      if( newEd != nullptr ) {
        return true;
      }
    }
  }
  if( msging( _T( "文件似乎不存在。您想创建它吗?" ),
              _T( "错误" ), wxICON_QUESTION | wxYES_NO ) == wxID_YES ) {
    project = Manager::Get()->GetProjectManager()->GetActiveProject();
    if( project ) {
      wxSetWorkingDirectory( project->GetBasePath() );
    }
    if( ft == ftHeader ) {
      theFile.SetExt( FileFilters::CPP_EXT );
    } else if( ft == ftSource || ft == ftTemplateSource ) {
      theFile.SetExt( FileFilters::H_EXT );
    }
    M_Code_Editor* newEd = New( theFile.GetFullPath() );
    if( project ) {
      if( msging( _T( "是否要在活动项目中添加此新文件?" ),
                  _T( "将文件添加到项目" ),
                  wxYES_NO | wxICON_QUESTION ) == wxID_YES ) {
        wxArrayInt targets;
        if( Manager::Get()->GetProjectManager()->AddFileToProject( newEd->GetFilename(), project, targets ) != 0 ) {
          ProjectFile* pf = project->GetFileByFilename( newEd->GetFilename(), false );
          newEd->SetProjectFile( pf );
          Manager::Get()->GetProjectManager()->GetUI().RebuildTree();
        }
      }
    }
    CheckForExternallyModifiedFiles();
  }
  return false;
}

void EditorManager::OnGenericContextMenuHandler( wxCommandEvent& event ) {
  EditorBase* eb = GetActiveEditor();
  M_Code_Editor* ed = GetBuiltinEditor( eb );
  int id = event.GetId();
  if( id == idNBTabSplitHorz && ed ) {
    ed->Split( M_Code_Editor::stHorizontal );
  } else if( id == idNBTabSplitVert && ed ) {
    ed->Split( M_Code_Editor::stVertical );
  } else if( id == idNBTabUnsplit && ed ) {
    ed->Unsplit();
  } else if( id >= idNBSwitchFile1 && id <= idNBSwitchFileMax ) {
    eb = GetEditor( id - idNBSwitchFile1 );
    if( eb ) {
      SetActiveEditor( eb );
    }
  }
}

void EditorManager::OnPageChanged( wxAuiNotebookEvent& event ) {
  EditorBase* eb = static_cast<EditorBase*>( m_pNotebook->GetPage( event.GetSelection() ) );
  EditorBase* eb_old = nullptr;
  if( event.GetOldSelection() != -1 ) {
    eb_old = static_cast<EditorBase*>( m_pNotebook->GetPage( event.GetOldSelection() ) );
  }
  CodeBlocksEvent evt( cbEVT_EDITOR_SWITCHED, -1, nullptr, eb, nullptr, eb_old );
  Manager::Get()->GetPluginManager()->NotifyPlugins( evt );
  CodeBlocksEvent evt2( cbEVT_EDITOR_ACTIVATED, -1, nullptr, eb );
  Manager::Get()->GetPluginManager()->NotifyPlugins( evt2 );
  if( Manager::Get()->GetConfigManager( _T( "app" ) )->ReadBool( _T( "/environment/tabs_stacked_based_switching" ) ) ) {
    wxWindow*        wnd;
    cbNotebookStack* body;
    cbNotebookStack* tmp;
    wnd = m_pNotebook->GetPage( event.GetSelection() );
    for( body = m_pNotebookStackHead; body->next != nullptr; body = body->next ) {
      if( wnd == body->next->window ) {
        if( m_pNotebookStackTail == body->next ) {
          m_pNotebookStackTail = body;
        }
        tmp = body->next;
        body->next = tmp->next;
        tmp->next = m_pNotebookStackHead->next;
        m_pNotebookStackHead->next = tmp;
        break;
      }
    }
    if( ( m_pNotebookStackHead->next == nullptr )
        || ( wnd != m_pNotebookStackHead->next->window ) ) {
      body = new cbNotebookStack( wnd );
      body->next = m_pNotebookStackHead->next;
      m_pNotebookStackHead->next = body;
      ++m_nNotebookStackSize;
    }
  }
  m_pData->m_SetFocusFlag = true;
  event.Skip();
}

void EditorManager::OnPageChanging( wxAuiNotebookEvent& event ) {
  int old_sel = event.GetOldSelection();
  if( old_sel != -1 && static_cast<size_t>( old_sel ) < m_pNotebook->GetPageCount() ) {
    EditorBase* eb = static_cast<EditorBase*>( m_pNotebook->GetPage( old_sel ) );
    CodeBlocksEvent evt( cbEVT_EDITOR_DEACTIVATED, -1, nullptr, eb );
    Manager::Get()->GetPluginManager()->NotifyPlugins( evt );
  }
  event.Skip();
}

void EditorManager::OnPageClose( wxAuiNotebookEvent& event ) {
  int sel = event.GetSelection();
  bool doClose = false;
  EditorBase* eb = nullptr;
  if( sel != -1 ) {
    event.Veto();
    eb = static_cast<EditorBase*>( m_pNotebook->GetPage( sel ) );
    if( QueryClose( eb ) ) {
      doClose = true;
      if( m_pNotebook->GetPageCount() <= 1 ) {
        CodeBlocksEvent evt( cbEVT_EDITOR_SWITCHED, -1, nullptr, nullptr, nullptr, eb );
        Manager::Get()->GetPluginManager()->NotifyPlugins( evt );
      }
    }
  }
  if( Manager::Get()->GetConfigManager( _T( "app" ) )->ReadBool( _T( "/environment/tabs_stacked_based_switching" ) ) ) {
    wxWindow* wnd;
    cbNotebookStack* body;
    cbNotebookStack* tmp;
    wnd = m_pNotebook->GetPage( event.GetSelection() );
    for( body = m_pNotebookStackHead; body->next != nullptr; body = body->next ) {
      if( wnd == body->next->window ) {
        tmp = body->next;
        body->next = tmp->next;
        delete tmp;
        --m_nNotebookStackSize;
        break;
      }
    }
  }
  if( doClose && eb != nullptr ) {
    Close( eb );
  } else {
    event.Skip();
  }
}

void EditorManager::OnPageContextMenu( wxAuiNotebookEvent& event ) {
  if( event.GetSelection() == -1 ) {
    return;
  }
  if( wxGetKeyState( WXK_CONTROL ) && GetEditorsCount() > 1 ) {
    wxMenu* pop = new wxMenu;
    EditorBase* current = GetActiveEditor();
    for( int i = 0; i < EditorMaxSwitchTo && i < GetEditorsCount(); ++i ) {
      EditorBase* other = GetEditor( i );
      if( !other ) {
        continue;
      }
      const wxString name = ( other->GetModified() ? _T( "*" ) : wxEmptyString ) + other->GetShortName();
      if( other == current ) {
        pop->AppendCheckItem( wxID_ANY, name );
        pop->FindItemByPosition( pop->GetMenuItemCount() - 1 )->Check();
      } else
      { pop->Append( idNBSwitchFile1 + i, name ); }
    }
    m_pNotebook->PopupMenu( pop );
    delete pop;
    return;
  }
  m_pNotebook->SetSelection( event.GetSelection() );
  wxMenu* pop = new wxMenu;
  pop->Append( idNBTabClose, _T( "关闭" ) );
  pop->Append( idNBTabCloseAll, _T( "关闭全部" ) );
  pop->Append( idNBTabCloseAllOthers, _T( "互斥关闭" ) );
  wxAuiTabCtrl *activeTabCtrl = m_pNotebook->GetTabCtrl( GetActiveEditor() );
  if( !activeTabCtrl || activeTabCtrl->GetPageCount() <= 1 ) {
    pop->Enable( idNBTabCloseAll, false );
    pop->Enable( idNBTabCloseAllOthers, false );
  }
  int any_modified = 0;
  for( int i = 0; i < GetEditorsCount(); ++i ) {
    EditorBase* ed = GetEditor( i );
    if( ed && ed->GetModified() ) {
      if( ++any_modified > 1 ) {
        break;
      }
    }
  }
  pop->Append( idNBTabSave, _T( "保存" ) );
  pop->Append( idNBTabSaveAll, _T( "保存全部" ) );
  pop->Enable( idNBTabSave, false );
  pop->Enable( idNBTabSaveAll, false );
  if( any_modified > 0 ) {
    if( GetEditor( event.GetSelection() )->GetModified() ) {
      pop->Enable( idNBTabSave, true );
    }
    if( any_modified > 1 || !GetEditor( event.GetSelection() )->GetModified() ) {
      pop->Enable( idNBTabSaveAll, true );
    }
  }
  M_Code_Editor* ed = GetBuiltinEditor( event.GetSelection() );
  if( ed ) {
    pop->Append( idNBSwapHeaderSource, _T( "Swap header/source" ) );
    pop->Append( idNBTabOpenContainingFolder, _T( "定位文件" ) );
    pop->Append( idNBProperties, _T( "属性" ) );
  }
  if( Manager::Get()->GetConfigManager( _T( "app" ) )->ReadBool( _T( "/environment/editor_tabs_bottom" ), false ) ) {
    pop->Append( idNBTabTop, _T( "Tabs at top" ) );
  } else {
    pop->Append( idNBTabBottom, _T( "Tabs at bottom" ) );
  }
  if( ed ) {
    wxMenu* splitMenu = new wxMenu;
    if( ed->GetSplitType() != M_Code_Editor::stHorizontal ) {
      splitMenu->Append( idNBTabSplitHorz, _T( "水平分割" ) );
    }
    if( ed->GetSplitType() != M_Code_Editor::stVertical ) {
      splitMenu->Append( idNBTabSplitVert, _T( "垂直分割" ) );
    }
    if( ed->GetSplitType() != M_Code_Editor::stNoSplit ) {
      splitMenu->Append( idNBTabUnsplit, _T( "还原" ) );
    }
    pop->Append( -1, _T( "分割视图" ), splitMenu );
    if( Manager::Get()->GetProjectManager()->GetActiveProject() ) {
      ProjectFile *projectFile = ed->GetProjectFile();
      if( projectFile ) {
        pop->Append( idNBRemoveFileFromProject, _T( "从项目中移除此文件" ) );
        pop->Append( idNBShowFileInTree, _T( "从项目中定位此文件" ) );
      } else {
        pop->Append( idNBAddFileToProject, _T( "从项目中添加此文件" ) );
      }
    }
  }
  Manager::Get()->GetPluginManager()->AskPluginsForModuleMenu( mtEditorTab, pop );
  m_pNotebook->PopupMenu( pop );
  delete pop;
}

void EditorManager::OnClose( M_Code_unused wxCommandEvent& event ) {
  Close( GetActiveEditor() );
}

void EditorManager::OnCloseAll( M_Code_unused wxCommandEvent& event ) {
  CloseAllInTabCtrl();
}

void EditorManager::OnCloseAllOthers( M_Code_unused wxCommandEvent& event ) {
  CloseAllInTabCtrlExcept( GetActiveEditor() );
}

void EditorManager::OnSave( M_Code_unused wxCommandEvent& event ) {
  Save( m_pNotebook->GetSelection() );
}

void EditorManager::OnSaveAll( M_Code_unused wxCommandEvent& event ) {
  SaveAll();
}

void EditorManager::OnSwapHeaderSource( M_Code_unused wxCommandEvent& event ) {
  SwapActiveHeaderSource();
}

void EditorManager::OnOpenContainingFolder( M_Code_unused wxCommandEvent& event ) {
  OpenContainingFolder();
}

void EditorManager::OnTabPosition( wxCommandEvent& event ) {
  long style = m_pNotebook->GetWindowStyleFlag();
  style &= ~wxAUI_NB_BOTTOM;
  if( event.GetId() == idNBTabBottom ) {
    style |= wxAUI_NB_BOTTOM;
  }
  m_pNotebook->SetWindowStyleFlag( style );
  m_pNotebook->Refresh();
  Manager::Get()->GetConfigManager( _T( "app" ) )->Write( _T( "/environment/editor_tabs_bottom" ), ( bool )( style & wxAUI_NB_BOTTOM ) );
}

void EditorManager::OnProperties( M_Code_unused wxCommandEvent& event ) {  // 单文件属性
  M_Code_Editor* ed = GetBuiltinActiveEditor();
  ProjectFile* pf = nullptr;
  if( ed ) {
    pf = ed->GetProjectFile();
  }
  if( pf ) {
    pf->ShowOptions( Manager::Get()->GetAppWindow() );
  } else {
    ProjectFileOptionsDlg dlg( Manager::Get()->GetAppWindow(), GetActiveEditor()->GetFilename() ,nullptr);
    PlaceWindow( &dlg );
    dlg.ShowModal();
  }
}

void EditorManager::OnAddFileToProject( M_Code_unused wxCommandEvent& event ) {
  M_Code_Project *project = Manager::Get()->GetProjectManager()->GetActiveProject();
  wxString fname = GetBuiltinActiveEditor()->GetFilename();
  wxArrayInt targets;
  if( Manager::Get()->GetProjectManager()->AddFileToProject( fname, project, targets ) != 0 ) {
    ProjectFile* pf = project->GetFileByFilename( fname, false );
    GetBuiltinActiveEditor()->SetProjectFile( pf );
    Manager::Get()->GetProjectManager()->GetUI().RebuildTree();
  }
}

void EditorManager::OnRemoveFileFromProject( M_Code_unused wxCommandEvent& event ) {
  ProjectFile* pf = GetBuiltinActiveEditor()->GetProjectFile();
  if( pf ) {
    M_Code_Project *project = pf->GetParentProject();
    Manager::Get()->GetProjectManager()->RemoveFileFromProject( pf, project );
    Manager::Get()->GetProjectManager()->GetUI().RebuildTree();
  }
}

void EditorManager::OnShowFileInTree( M_Code_unused wxCommandEvent& event ) {
  ProjectFile* pf = GetBuiltinActiveEditor()->GetProjectFile();
  if( pf ) {
    cbProjectManagerUI &ui = Manager::Get()->GetProjectManager()->GetUI();
    ui.SwitchToProjectsPage();
    ui.ShowFileInTree( *pf );
  }
}

void EditorManager::OnAppDoneStartup( wxCommandEvent& event ) {
  event.Skip();
}

void EditorManager::OnAppStartShutdown( wxCommandEvent& event ) {
  event.Skip();
}

void EditorManager::OnCheckForModifiedFiles( M_Code_unused wxCommandEvent& event ) {
  CheckForExternallyModifiedFiles();
}

void EditorManager::HideNotebook() {
  if( m_pNotebook ) {
    m_pNotebook->Hide();
  }
}

void EditorManager::ShowNotebook() {
  if( m_pNotebook ) {
    m_pNotebook->Show();
  }
}

void EditorManager::OnUpdateUI( wxUpdateUIEvent& event ) {
  if( !Manager::Get()->IsAppShuttingDown() && m_pData->m_SetFocusFlag ) {
    M_Code_Editor* ed = GetBuiltinActiveEditor();
    if( ed ) {
      ed->GetControl()->SetFocus();
    }
    m_pData->m_SetFocusFlag = false;
  }
  event.Skip();
}

void EditorManager::CollectDefines( CodeBlocksEvent& event ) {
  M_Code_Project* prj = Manager::Get()->GetProjectManager()->GetActiveProject();
  if( !prj || !Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/track_preprocessor" ),  true )
      || !Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/collect_prj_defines" ), true ) ) {
    event.Skip();
    return;
  }
  wxArrayString compilerFlags = prj->GetCompilerOptions();
  ProjectBuildTarget* tgt = prj->GetBuildTarget( prj->GetActiveBuildTarget() );
  FilesList* lst;
  wxString id;
  if( tgt ) {
    AppendArray( tgt->GetCompilerOptions(), compilerFlags );
    lst = &tgt->GetFilesList();
    id  = tgt->GetCompilerID();
  } else {
    lst = &prj->GetFilesList();
    id  = prj->GetCompilerID();
  }
  Compiler* comp = CompilerFactory::GetCompiler( id );
  if( comp ) {
    AppendArray( comp->GetCompilerOptions(), compilerFlags );
  }
  wxArrayString defines;
  for( size_t i = 0; i < compilerFlags.GetCount(); ++i ) {
    if( compilerFlags[i].StartsWith( _T( "-D" ) )
        || compilerFlags[i].StartsWith( _T( "/D" ) ) ) {
      defines.Add( compilerFlags[i].Mid( 2 ) );
    } else if( compilerFlags[i].Find( _T( "`" ) ) != wxNOT_FOUND ) {
      wxString str = compilerFlags[i];
      ExpandBackticks( str );
      str.Replace( _T( "`" ), _T( " " ) );
      AppendArray( GetArrayFromString( str, _T( " " ) ), compilerFlags );
    } else if( compilerFlags[i] == _T( "-ansi" )
               || compilerFlags[i] == _T( "-std=c90" )
               || compilerFlags[i] == _T( "-std=c++98" ) ) {
      defines.Add( _T( "__STRICT_ANSI__" ) );
    }
  }
  defines.Add( _T( "__cplusplus" ) );
  for( FilesList::iterator it = lst->begin(); it != lst->end(); ++it ) {
    if( ( *it )->relativeFilename.EndsWith( _T( ".c" ) ) ) {
      defines.RemoveAt( defines.GetCount() - 1 );
      break;
    }
  }
  if( id.Find( _T( "gcc" ) ) != wxNOT_FOUND ) {
    defines.Add( _T( "__GNUC__" ) );
    defines.Add( _T( "__GNUG__" ) );
  }
  if( Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/platform_defines" ), false ) ) {
    defines.Add( _T( "LINUX" ) );
    defines.Add( _T( "linux" ) );
    defines.Add( _T( "__linux" ) );
    defines.Add( _T( "__linux__" ) );
    defines.Add( _T( "unix" ) );
    defines.Add( _T( "__unix" ) );
    defines.Add( _T( "__unix__" ) );
    defines.Add( _T( "__UNIX__" ) );
    defines.Add( _T( "__WXGTK__" ) );
    defines.Add( _T( "__amd64" ) );
    defines.Add( _T( "__amd64__" ) );
    defines.Add( _T( "__x86_64" ) );
    defines.Add( _T( "__x86_64__" ) );
    defines.Add( _T( "__IA64__" ) );
  }
  const wxString keywords = GetStringFromArray( MakeUniqueArray( defines, true ), _T( " " ), false );
  const HighlightLanguage hlCpp = m_Theme->GetHighlightLanguage( _T( "C/C++" ) );
  if( m_Theme->GetKeywords( hlCpp, 4 ) == keywords ) {
    return;
  }
  m_Theme->SetKeywords( hlCpp, 4, keywords );
  const wxString key = _T( "/colour_sets/" ) + m_Theme->GetName() + _T( "/cc/" );
  Manager::Get()->GetConfigManager( _T( "editor" ) )->Write( key + _T( "editor/keywords/set4" ), keywords );
  Manager::Get()->GetConfigManager( _T( "editor" ) )->Write( key + _T( "name" ), _T( "C/C++" ) );
  for( int index = 0; index < GetEditorsCount(); ++index ) {
    M_Code_Editor* ed = GetBuiltinEditor( index );
    if( ed && ( ed->GetLanguage() == hlCpp ) ) {
      M_Code_StyledTextCtrl* stc = ed->GetControl();
      stc->SetKeyWords( 4, keywords );
    }
  }
  event.Skip();
}

void EditorManager::SetZoom( int zoom ) {
  m_Zoom = zoom;
}

int EditorManager::GetZoom() const {
  return m_Zoom;
}

